﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Antlr.Runtime;
using PSLib.Parsing.Common;

namespace PSLib.Parsing.Common.Search
{
    /// <summary>
    /// Class introduce search operations with token stream
    /// </summary>
    public class TokensStreamSearchProvider
    {
        #region Constructor

        internal TokensStreamSearchProvider(CommonTokenStream stream)
        {
            this.TokenStream = stream;
        }

        #endregion

        #region Recognize members

        public RecognizedToken Recognize(int tokenIndex)
        {
            RecognizedToken result = null;
            if (tokenIndex >= 0 && tokenIndex < this.TokenStream.Count)
            {
                result = TokensRecognizer.Recognize(this.TokenStream.Get(tokenIndex));
            }
            return result;
        }

        public RecognizedTokenRange Recognize(int tokenStartIndex, int tokenEndIndex)
        {
            RecognizedTokenRange result = new RecognizedTokenRange(this.TokenStream);
            result.Start = this.Recognize(tokenStartIndex);
            result.End = this.Recognize(tokenEndIndex);

            return result;
        }

        #endregion

        #region Find members

        public RecognizedTokenRange Find(ElementSpan span)
        {
            List<object> tokenList = new List<object>(new GenericEnumerable<object>(this.TokenStream.GetTokens()));
            ElementSpanComparer comparer = new ElementSpanComparer(this.TokenStream);
            int intersectPosition = tokenList.BinarySearch(span, comparer);
            int startPosition = tokenList.LinearRangeSideSearch(span, intersectPosition, SearchDirection.Backward, comparer);
            int endPosition = tokenList.LinearRangeSideSearch(span, intersectPosition, SearchDirection.Forward, comparer);
            return this.Recognize(startPosition, endPosition);
        }

        public RecognizedToken Find(ElementPosition position)
        {
            List<object> tokenList = new List<object>(new GenericEnumerable<object>(this.TokenStream.GetTokens()));
            
            ElementPositionComparer comparer = new ElementPositionComparer(this.TokenStream);
            int intersectPosition = tokenList.BinarySearch(position, comparer);
            return this.Recognize(intersectPosition);
        }

        #endregion

        #region Get members

        public RecognizedToken Get(RecognizedToken startToken, SearchDirection direction, bool includeHidden = false)
        {
            return this.Get(startToken,direction == SearchDirection.Forward ? 1 : -1, includeHidden);
        }

        public RecognizedToken Get(RecognizedToken startToken, int tokenIndexOffset, bool includeHidden = false)
        {
            if (startToken == null) return null;
            if (tokenIndexOffset == 0) return startToken;

            int step = 1;
            if (tokenIndexOffset < 0) step = -1;

            RecognizedToken token = null;
            for (int i = startToken.NativeToken.TokenIndex + step, offs = 0; offs < Math.Abs(tokenIndexOffset); i += step)
            {
                token = this.Recognize(i);
                if (token == null) break;
                else if (token.NativeToken.Channel == 0 || includeHidden) offs++;
            }

            return token;
        }

        #endregion

        #region Query members

        public bool Query(RecognizedToken startToken, SearchDirection direction, bool includeHidden, Query query, out RecognizedToken result)
        {
            result = this.Query(startToken, direction, includeHidden, query);
            return result != null;
        }

        public RecognizedToken Query(RecognizedToken startToken, SearchDirection direction, bool includeHidden, Query query)
        {
            RecognizedToken resultToken = null;
            RecognizedToken curToken = this.Get(startToken,direction,includeHidden);

            for (int i = 0; i < query.Sequence.Count && curToken != null; i++)
            {
                int directionIndex = (direction == SearchDirection.Forward) ? i : query.Sequence.Count - i - 1;
                var curSequenceArgument = query.Sequence[directionIndex];
                if (!curSequenceArgument.Compare(curToken)) return null;
                if (curSequenceArgument.Selectable) resultToken = curToken;

                curToken = this.Get(curToken, direction, includeHidden);
            }
            return resultToken;
        }

        #endregion

        

        public CommonTokenStream TokenStream { get; private set; }
    }
}
